package com.aldobucchi.core.os.thread
{
	
	
	import flash.events.IEventDispatcher;
	import flash.utils.getTimer;
	
	import mx.events.StateChangeEvent;
	import mx.logging.ILogger;
	
	import com.aldobucchi.core.os.RemoteTargetQueue;
	import com.aldobucchi.core.os.ThreadScheduler;
	import com.aldobucchi.core.os.call.AsyncCall;
	import com.aldobucchi.core.os.call.AsyncCallWithListener;
	import com.aldobucchi.core.os.call.CallState;
	import com.aldobucchi.core.os.call.ICall;
	import com.aldobucchi.core.os.call.SyncCall;
	import com.aldobucchi.utils.StateHolder;
	import com.aldobucchi.utils.priority.IPrioritizable;
	import com.aldobucchi.utils.priority.PriorityEvent;
	import com.aldobucchi.x.mx.logging.Log.LogUtil;




/**
 *  
 * 
 *  @eventType com.aldobucchi.utils.priority.PriorityEvent.PRIORITY_CHANGED
 */
[Event(name="priorityChanged", type="com.aldobucchi.utils.priority.PriorityEvent")]		




	/**
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */
	public class Thread extends StateHolder implements IPrioritizable
	{



		public static const MIN_PRIORITY	:uint = 1;

		public static const NORM_PRIORITY	:uint = 5;

		public static const MAX_PRIORITY	:uint = 10;





		/////////////////////////////////////////////
		// elapsed
		/////////////////////////////////////////////

		public var elapsed:int=0;		





		/////////////////////////////////////////////
		// priority
		/////////////////////////////////////////////


		private var _priority:uint;
		[Bindable( "priorityChanged" )]
		public function get priority( ):uint
		{
			return _priority;
		}
		public function set priority( v:uint ):void
		{
			if ( v == _priority )
				return;

			 _priority = v;

			if ( willTrigger( PriorityEvent.PRIORITY_CHANGED ) )
				dispatchEvent( new PriorityEvent( PriorityEvent.PRIORITY_CHANGED ) );
		}









		private var logger:ILogger;
		
		



		/**
		 * 
		 * The stack is an array of queues.
		 * 
		 */
		private var stack:Array = [ [] ];
		


		/**
		 * 
		 * The current queue is located at the end of the stack
		 * 
		 * @return 
		 * 
		 */
		private function get currentQueue( ):Array
		{
			if ( stack.length == 0 )
				return null;
				
			return stack[ stack.length - 1 ] as Array;
		}







		public function Thread( priority:uint = NORM_PRIORITY )
		{
			
			super( ThreadState.CREATED );
			
			_priority = priority;
			
			logger = LogUtil.getStandardLogger( this );		
		
		}



		public function start( ):void
		{
			
			if ( currentState == ThreadState.CREATED )
			{
				setState( ThreadState.RUNNING );
				ThreadScheduler.i.queue( this );
			} 
			
		}



		public function kill( ):void
		{
			setState( ThreadState.TERMINATED );
		}




		/**
		 * 
		 * Queues a function so that it will be executed next.
		 * 
		 * @param func
		 * @param args
		 * 
		 */		 
		public function queue( func:Function, args:Array = null ):void
		{
			queueCall( new SyncCall( this, func, args ) );
		}


		
		/**
		 * 
		 * Queues a function that will be executed in what is assumed to be an async operation.
		 * Event listeners will be used to determine when the operation finishes.
		 * More than one can be passed in case you are expecting one of a set of events ( result OR fault for example ).
		 * 
		 * 
		 * @param func
		 * @param args
		 * @param eventListenerSpecs
		 * @param processingTarget
		 * 
		 */		
		public function queueAsyncUsingListener( func:Function, args:Array = null, dispatcher:IEventDispatcher=null, eventListenerSpecs:Array=null, processingTarget:*=undefined ):void
		{
			queueCall( new AsyncCallWithListener( this, func, args, dispatcher, eventListenerSpecs, processingTarget ) );
		}
		

		
		
		public function queueAsyncUsingResponder( func:Function, args:Array, responderIndexInArgs:int, processingTarget:*=undefined ):void
		{
			throw new Error( "Async Call Using Responder not implemented yet" );
		}


		private function queueCall( call:ICall ):void
		{
			currentQueue.push( call );
		}







		private function getNextCall( ):ICall
		{
			
			if ( currentState == ThreadState.TERMINATED )
			{
				return null;			
			}


			while ( currentQueue.length == 0 )
			{
				// the current queue has no more pending calls left, we can pop it and move down the stack
				stack.pop( );
				if ( currentQueue == null )	
				{
					// the stack is exhausted, we're done
					setState( ThreadState.TERMINATED );
					return null;
				}		
			}
			
			
			return currentQueue.shift( ) as ICall;		
		
		}


		
		
		
		
		
		
		/**
		 * 
		 * consumes one step from the queue
		 * 
		 */		
		public function step( ):void
		{

			var call:ICall = getNextCall( );
			
			if ( call == null )
			{
				setState( ThreadState.TERMINATED );
				return;							
			}


			setState( ThreadState.RUNNING );

			// append to the stack so any calls made when executing this entry
			// are stored in their own queue
			stack.push( [] );
			
			
			
			//////////////// execute //////////////////
			
			runnerStack.push( this );
			
			if ( call.isSync )
			{
				call.execute( );
				runnerStack.pop( );
			}
			else
			{

				setState( ThreadState.BLOCKED ); // we are blocked while waiting for an async call

				call.addEventListener( StateChangeEvent.CURRENT_STATE_CHANGE, call_currentStateChange );

				var rtq:RemoteTargetQueue = RemoteTargetQueue.forTarget( ( call as AsyncCall ).processingTarget );		

				rtq.queueCall( call );

			}

		}
		
		
		
		
		/**
		 * 
		 * The currently executing async call has completed
		 * 
		 * @param event
		 * 
		 */		
		private function call_currentStateChange( event:StateChangeEvent ):void
		{
			
			
			var call:ICall = event.target as ICall;
			
			if ( event.newState == CallState.COMPLETE )
			{
				call.removeEventListener( StateChangeEvent.CURRENT_STATE_CHANGE, call_currentStateChange );
				setState( ThreadState.RUNNING );
				runnerStack.pop( );			

			}		
		
		}
		
		
		
		
		
		
		
		private var endTime:int;
		/**
		 * 
		 * Will execute this thread for ( a bit longer than ) the amount 
		 * of time specified.
		 * 
		 * if no argument is passed it will run to completion
		 * 
		 * @param elapsed the time, in milliseconds, that this thread is given to run. defaults to -1 ( no limit )
		 * 
		 */		
		public function run( elapsed:int = -1 ):void
		{
			endTime = getTimer( ) + elapsed;
			while (  ( elapsed == -1 ) || ( getTimer( ) < endTime ) )
			{
				
				step( );
				
				if ( currentState == ThreadState.TERMINATED )
					break;
					
				if ( currentState == ThreadState.BLOCKED )
					break;
					
			}
		
		}
		
		
		
		
		
		
		
		////////////////////////////////////////////////////////////////////
		///////////////////////////// static ///////////////////////////////
		////////////////////////////////////////////////////////////////////
		


		/**
		 * 
		 * Note that this logger is static. Not the same as the one above which is dynamic ( per instance ).
		 * 
		 */
		private static var staticLogger:ILogger = LogUtil.getStandardLogger( Thread );


		/**
		 * 
		 * By using a stack here we allow for nested async runners 
		 * ( a runner generated within another runner )
		 * 
		 * We are still missing some logic to chain runners, but
		 * there are no use cases yet so we don't really care
		 * 
		 */
		private static var runnerStack:Array = [];

		
		
		
		
		/**
		 * 
		 * 
		 * Creates a thread instance, starting at the given function.
		 * You can then call runner.step() to execute the first step and any
		 * other nested steps that get queued as well.
		 * 
		 * 
		 * @param func
		 * @param args
		 * @return 
		 * 
		 */		
		public static function create( func:Function, args:Array = null, priority:uint = NORM_PRIORITY ):Thread
		{
			var thread:Thread = new Thread( priority );
			thread.queue( func, args );
			return thread;
		}
		
		
		
		
		public static function get current( ):Thread
		{
			if ( runnerStack.length == 0 )// this is most likely a coding error, it will be thrown somewhere above
				return null;
				
			return ( runnerStack[ runnerStack.length - 1 ] as Thread );
										
		}

		
	}
}






